﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace GameLogic.Logic
{
    public class GameLoop : Behavior<FrameworkElement>
    {
        // Collision lists
        private List<CollisionInformation> collisions;
        private Dictionary<FrameworkElement, ICollidable> movingCollidableElements = new Dictionary<FrameworkElement, ICollidable>();
        private Dictionary<FrameworkElement, ICollidable> staticCollidableElements = new Dictionary<FrameworkElement, ICollidable>();
        private List<FrameworkElement> movingCollidableElementsToRemove = new List<FrameworkElement>();
        private List<FrameworkElement> staticCollidableElementsToRemove = new List<FrameworkElement>();

        private bool isRunning;
        private Random random = new Random();

        // Events handeled by game behaviors (GameLoopBehavior)
        public event EventHandler Update;
        public event EventHandler Reset;
        public event EventHandler<CollisionEventArgs> Collision;


        public FrameworkElement RootElement
        {
            get { return this.AssociatedObject; }
        }


        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Loaded += this.OnAssociatedObjectLoaded;
        }

        void OnAssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            // Create Rendering EventHandler to fire off the GameLoop on every frame drawn on the screen
            CompositionTarget.Rendering += new EventHandler(OnRenderUpdate);

            isRunning = false;
        }


        //
        // Methods to manipulate the game loop from within the game
        //
        public void StartGameLoop()
        {
            isRunning = true;
        }

        public void PauseGameLoop()
        {
            isRunning = false;
        }

        public void ResetGameLoop()
        {
            isRunning = false;

            if (this.Reset != null)
            {
                this.Reset(this, EventArgs.Empty);
            }
        }

        //
        // Render Update (GameLoop)
        //
        private void OnRenderUpdate(object sender, EventArgs e)
        {
            if (isRunning)
            {
                // Update collision information
                this.CheckForCollisions();

                // Fire GameLoop event to update game objects and their behaviors
                if (this.Update != null)
                {
                    this.Update(this, null);
                }
            }
        }

        #region Collision Handling

        // Add FrameworkElement to the list of moving collidable Elements
        public void RegisterAsMovingCollidable(FrameworkElement collidable)
        {
            foreach (Behavior behavior in Interaction.GetBehaviors(collidable))
            {
                if (behavior is ICollidable && !this.movingCollidableElements.ContainsKey(collidable))
                {
                    this.movingCollidableElements.Add(collidable, (ICollidable)behavior);
                    break;
                }
            }
        }

        // Queue FrameworkElement to be removed from list of moving collidable Elements
        public void UnregisterAsMovingCollidable(FrameworkElement collidable)
        {
            if (!this.movingCollidableElementsToRemove.Contains(collidable))
            {
                this.movingCollidableElementsToRemove.Add(collidable);
            }
        }

        // Add FrameworkElement to the list of static collidable Elements
        public void RegisterAsStaticCollidable(FrameworkElement collidable)
        {
            foreach (Behavior behavior in Interaction.GetBehaviors(collidable))
            {
                if (behavior is ICollidable && !this.staticCollidableElements.ContainsKey(collidable))
                {
                    this.staticCollidableElements.Add(collidable, (ICollidable)behavior);
                    break;
                }
            }
        }

        // Queue FrameworkElement to be removed from list of static collidable Elements
        public void UnregisterAsStaticCollidable(FrameworkElement collidable)
        {
            if (!this.staticCollidableElementsToRemove.Contains(collidable))
            {
                this.staticCollidableElementsToRemove.Add(collidable);
            }
        }

        private void CheckForCollisions()
        {
            List<CollisionInformation> collisions = new List<CollisionInformation>();

            // Loop trough all moving collidable elements
            foreach (KeyValuePair<FrameworkElement, ICollidable> movingCollisionKeyValuePair in this.movingCollidableElements)
            {
                FrameworkElement movingCollidableElement = movingCollisionKeyValuePair.Key;
                ICollidable movingCollidable = movingCollisionKeyValuePair.Value;
                Rect movingCollidableRect = movingCollidable.Rect;

                // Check for collision with all static elements
                foreach (KeyValuePair<FrameworkElement, ICollidable> staticCollisionKeyValuePair in this.staticCollidableElements)
                {
                    FrameworkElement staticCollisionElement = staticCollisionKeyValuePair.Key;
                    ICollidable staticCollidable = staticCollisionKeyValuePair.Value;
                    Rect staticCollidableRect = staticCollidable.Rect;

                    // If a moving object has collision, don't check for collision with itself 
                    if (movingCollidableElement != staticCollisionElement)
                    {
                        staticCollidableRect.Intersect(movingCollidableRect);
                        if (staticCollidableRect != Rect.Empty)
                        {
                            // If collisionRect is not empty, a collision has occurred
                            if (this.Collision != null)
                            {
                                // Calculates the normal of the collision
                                Vector2D collisionVector;
                                double perturbX = ((random.NextDouble() - 0.5) * 0.05);
                                double perturbY = ((random.NextDouble() - 0.5) * 0.05);

                                if (staticCollidableRect.Width >= staticCollidableRect.Height)
                                {
                                    collisionVector = new Vector2D(perturbX, -1 + perturbY);
                                    collisionVector.Normalize();
                                }
                                else
                                {
                                    collisionVector = new Vector2D(-1 + perturbX, perturbY);
                                    collisionVector.Normalize();
                                }
                                // Add collision information to the list of collisions
                                collisions.Add(new CollisionInformation(movingCollidableElement, staticCollisionElement, staticCollidableRect, collisionVector));
                            }
                        }
                    }
                }
            }

            // If there is a collision, fire the collision event
            if (collisions.Count > 0 && this.Collision != null)
            {
                this.Collision(this, new CollisionEventArgs(collisions));
            }

            // Remove all queued elements from list of moving collidable elements
            for (int i = 0; i < this.movingCollidableElementsToRemove.Count; i++)
            {
                this.movingCollidableElements.Remove(this.movingCollidableElementsToRemove[i]);
            }
            this.movingCollidableElementsToRemove.Clear();

            // Remove all queued elements from list of static collidable elements
            for (int i = 0; i < this.staticCollidableElementsToRemove.Count; i++)
            {
                this.staticCollidableElements.Remove(this.staticCollidableElementsToRemove[i]);
            }
            this.staticCollidableElementsToRemove.Clear();
        }

        #endregion
    }
}
